CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
	const minSize = Math.min(w, h)
	if (r > minSize / 2) r = minSize / 2
	// 开始绘制
	this.beginPath()
	this.moveTo(x + r, y)
	this.arcTo(x + w, y, x + w, y + h, r)
	this.arcTo(x + w, y + h, x, y + h, r)
	this.arcTo(x, y + h, x, y, r)
	this.arcTo(x, y, x + w, y, r)
	this.closePath()
	return this
}

CanvasRenderingContext2D.prototype.roundFillRect = function (x, y, w, h, r) {
	const minSize = Math.min(w, h)
	if (r > minSize / 2) r = minSize / 2
	// 开始绘制
	this.beginPath()
	this.moveTo(x + r, y)
	this.arcTo(x + w, y, x + w, y + h, r)
	this.arcTo(x + w, y + h, x, y + h, r)
	this.arcTo(x, y + h, x, y, r)
	this.arcTo(x, y, x + w, y, r)
	this.fill()
	this.closePath()
	return this
}

CanvasRenderingContext2D.prototype.topRoundRect = function (x, y, w, h, r) {
	const minSize = Math.min(w, h)
	if (r > minSize / 2) r = minSize / 2
	// 开始绘制
	this.beginPath()
	this.moveTo(x + r, y)
	this.arcTo(x + w, y, x + w, y + h, r)
	this.arcTo(x + w, y + h, x + w, y + h, r)
	this.arcTo(x, y + h, x + w, y + h, r)
	this.arcTo(x, y, x + w, y, r)
	this.closePath()
	return this
}

CanvasRenderingContext2D.prototype.bottomRoundRect = function (x, y, w, h, r) {
	const minSize = Math.min(w, h)
	if (r > minSize / 2) r = minSize / 2
	// 开始绘制
	this.beginPath()
	this.moveTo(x, y)
	this.arcTo(x + w, y, x + w, y, r)
	this.arcTo(x + w, y + h, x, y + h, r)
	this.arcTo(x, y + h, x, y, r)
	this.arcTo(x, y, x, y, r)
	this.closePath()
	return this
}

CanvasRenderingContext2D.prototype.fillTextCustom = function (text, x, y, restOption) {
	const context = this
	let { maxWidth, width } = restOption || {}
	if (!maxWidth) {
		// 这里仅考虑水平排列
		maxWidth = window.Math.floor(context.measureText(text).width + 10)
	}
	if (!width) {
		return context.fillText(text, x, y, maxWidth)
	}
	const arrText = text.split('')
	const align = context.textAlign || 'left'
	const letterOuterWidth = window.Math.floor(width / arrText.length)
	// 根据水平对齐方式确定第一个字符的坐标
	if (align === 'center') {
		x = x - width / 2 + letterOuterWidth / 2
	} else if (align === 'right') {
		x = x - width
	}
	// 开始逐字绘制
	arrText.forEach((letter) => {
		context.fillText(letter, x, y)
		// 确定下一个字符的横坐标
		x = x + letterOuterWidth
	})
}

function getPixelRatio () {
	return window.devicePixelRatio || 1
}

function drawRoundImage (options) {
	const imgurl = options.url
	const width = options.width || 200
	const height = options.height || 200
	const radius = options.radius
	const roundType = options.roundType || 'round'
	const origin = options.origin || true
	const ratio = options.ratio || getPixelRatio()

	return new Promise(function (resolve, reject) {
		const img = new Image()
		if (origin) {
			img.crossOrigin = 'Anonymous'
		}
		const imgCanvas = document.createElement('canvas')
		const c = imgCanvas.getContext('2d')
		imgCanvas.width = width * ratio
		imgCanvas.height = height * ratio
		const patternCanvas = document.createElement('canvas')
		const p = patternCanvas.getContext('2d')
		patternCanvas.width = width * ratio
		patternCanvas.height = height * ratio

		img.onload = function () {
			try {
				c.drawImage(img, 0, 0, width * ratio, height * ratio)
				const pattern = p.createPattern(imgCanvas, 'no-repeat')

				if (roundType === 'top') {
					p.topRoundRect(0, 0, width * ratio, height * ratio, radius * ratio)
				} else if (roundType === 'bottom') {
					p.bottomRoundRect(0, 0, width * ratio, height * ratio, radius * ratio)
				} else if (roundType === 'all') {
					p.roundRect(0, 0, width * ratio, height * ratio, radius * ratio)
				}
				p.fillStyle = pattern
				p.fill()
				resolve(patternCanvas)
			} catch (error) {
				console.error('drawRoundImage error ', imgurl, error)
				resolve(imgCanvas)
			}
		}
		img.onerror = function (error) {
			console.error('img error ', imgurl, error)
			resolve(imgCanvas)
		}
		img.src = imgurl
	})
}

function offscreenDrawImage (options) {
	let ratio = options.ratio || 1
	if (options.roundType) {
		return drawRoundImage(options)
	} else {
		const imgurl = options.url
		const width = options.width
		const height = options.height
		const origin = options.origin || true
		return new Promise(function (resolve, reject) {
			let img = new Image()
			let canvas = document.createElement('canvas')
			let ctx = canvas.getContext('2d')
			canvas.width = width * ratio
			canvas.height = height * ratio

			if (origin) {
				img.crossOrigin = 'Anonymous'
			}
			img.onload = function () {
				try {
					ctx.drawImage(img, 0, 0, width * ratio, height * ratio)
					resolve(canvas)
				} catch (error) {
					resolve(canvas)
				}
			}
			img.onerror = function (error) {
				resolve(error)
			}
			img.src = imgurl
		})
	}
}

function getStringLength (str) {
	let i = 0
	let code
	let len = 0
	for (; i < str.length; i++) {
		code = str.charCodeAt(i)
		if (code < 0x007f) {
			len += 0.5
		} else if (code >= 0x0080) {
			len += 1
		}
	}
	return Math.ceil(len)
}

function getSpace (text) {
	let length = getStringLength(text)
	let str = new Array(length + 1).join('　') + ' '
	return str
}

function drawText (textOptions, ctx) {
	const ratio = textOptions.ratio || 1
	const text = textOptions.text
	const pattern = /\{\{([^}]*)\}\}/g
	const textMatch = text.match(pattern)

	ctx.fillStyle = textOptions.color || '#000'
	if (textOptions.font) {
		ctx.font = textOptions.font
	} else {
		ctx.font = (textOptions.fontSize * ratio || '12') + 'px ' + (textOptions.fontWeight || '') + ' ' + (textOptions.fontFamily || 'sans-serif')
	}
	ctx.textAlign = textOptions.align || 'left'
	ctx.textBaseline = textOptions.textBaseline || 'top'

	if (textMatch) {
		let textAfter = text
		let xPos = []
		const hlText = []
		const placeholder = []
		textMatch.forEach(item => {
			let matches = pattern.exec(textAfter)
			let space = getSpace(matches[1])
			let t = ctx.measureText(RegExp.leftContext)
			placeholder.push(space)
			xPos.push(t.width)
			hlText.push(matches[1])
			textAfter = textAfter.replace(item, space)
		})
		let textAfterWidth = ctx.measureText(textAfter).width
		ctx.fillText(textAfter, textOptions.x * ratio, textOptions.y * ratio)
		if (ctx.textAlign === 'left') {
			xPos = xPos.map(item => {
				return item + textOptions.x * ratio
			})
		} else if (ctx.textAlign === 'center') {
			xPos = xPos.map(item => {
				return item + textOptions.x * ratio - textAfterWidth / 2
			})
		}
		ctx.textAlign = 'center'
		ctx.fillStyle = (textOptions.hl && textOptions.hl.color) || textOptions.color || '#000'
		if (textOptions.hl && textOptions.hl.font) {
			ctx.font = textOptions.hl.font
		} else if (textOptions.hl) {
			ctx.font = (textOptions.hl.fontSize * ratio || '12') + 'px ' + (textOptions.hl.fontWeight || '') + ' ' + (textOptions.hl.fontFamily || 'sans-serif')
		}
		hlText.forEach((item, index) => {
			const width = ctx.measureText(placeholder[index]).width
			ctx.fillText(item, xPos[index] + width / 2, textOptions.y * ratio)
		})
	} else {
		const { maxWidth, width } = textOptions
		const restTextOption = {
			maxWidth: maxWidth * ratio,
			width: width * ratio
		}
		ctx.fillTextCustom(textOptions.text, textOptions.x * ratio, textOptions.y * ratio, restTextOption)
	}
}

function drawRect (rectOption, ctx) {
	const { x, y, width, height, color } = rectOption
	ctx.save()
	ctx.beginPath()
	ctx.fillStyle = color
	ctx.fillRect(x, y, width, height)
	ctx.restore()
}

function drawCanvas (options) {
	if (!options.ele) {
		return Promise.reject(new Error('drawCanvas缺少 ele 参数'))
	}
	const ratio = options.ratio || getPixelRatio()
	const canvas = document.getElementById(options.ele)
	const ctx = canvas.getContext('2d')
	canvas.width = options.width * ratio
	canvas.height = options.height * ratio
	const drawConfig = options.draw || []
	if (options.background) {
		const rectOption = {
			x: 0,
			y: 0,
			width: canvas.width,
			height: canvas.height,
			color: options.background
		}
		drawRect(rectOption, ctx)
	}
	const drawImageArray = []
	drawConfig.forEach(item => {
		if (item.type === 'img') {
			item.ratio = ratio
			drawImageArray.push(offscreenDrawImage(item))
		}
	})

	return new Promise((resolve, reject) => {
		Promise.all(drawImageArray)
			.then(resultCanvas => {
				let imgIndex = 0
				drawConfig.forEach(item => {
					if (item.type === 'img') {
						ctx.drawImage(resultCanvas[imgIndex], item.x * ratio, item.y * ratio, item.width * ratio, item.height * ratio)
						imgIndex++
					} else if (item.type === 'canvas') {
						ctx.drawImage(item.canvas, item.x * ratio, item.y * ratio, item.width * ratio, item.height * ratio)
					} else if (item.type === 'fillRect') {
						ctx.save()
						ctx.fillStyle = item.color
						if (item.radius > 0) {
							ctx.roundFillRect(item.x * ratio, item.y * ratio, item.width * ratio, item.height * ratio, item.radius * ratio)
						} else {
							ctx.fillRect(item.x * ratio, item.y * ratio, item.width * ratio, item.height * ratio)
						}
						ctx.restore()
					} else if (item.type === 'text') {
						item.ratio = ratio
						drawText(item, ctx)
					}
				})
			})
			.then(res => {
				const fileQuality = (options.export && options.export.fileQuality) || 1
				let fileType = (options.export && options.export.fileType) || 'png'
				if (fileType === 'jpg') {
					fileType = 'jpeg'
				}
				fileType = 'image/' + fileType

				if (options.export && options.export.type === 'blob') {
					canvas.toBlob(function (blob) {
						resolve(blob)
					}, fileType, fileQuality)
				} else if (options.export && options.export.type === 'base64') {
					resolve(canvas.toDataURL(fileType, fileQuality))
				}
			})
			.catch(error => {
				console.error(error)
				reject(error)
			})
	})
}

export default drawCanvas
